package fun.werfamily.wf.user.core.domain.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import fun.werfamily.wf.base.dto.BaseUserInfoDTO;
import fun.werfamily.wf.base.util.TokenUtil;
import fun.werfamily.wf.user.core.domain.constant.RedisConstant;
import fun.werfamily.wf.user.core.domain.constant.RedisTimeoutConstants;
import fun.werfamily.wf.user.core.domain.dto.req.*;
import fun.werfamily.wf.user.core.domain.dto.resp.UserInfoDTO;
import fun.werfamily.wf.user.core.domain.enums.ResponseCodeEnum;
import fun.werfamily.wf.user.core.domain.enums.SmsTemplateEnum;
import fun.werfamily.wf.user.core.domain.exception.UserCoreException;
import fun.werfamily.wf.user.core.domain.service.IUserService;
import fun.werfamily.wf.user.core.domain.util.CaptchaUtil;
import fun.werfamily.wf.user.core.domain.util.RedisUtil;
import fun.werfamily.wf.user.core.domain.util.SmsMessageUtil;
import fun.werfamily.wf.user.core.infrastructure.dataobject.UserDO;
import fun.werfamily.wf.user.core.infrastructure.mapper.UserMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Objects;

/**
 * @Author: Mr.WenMing Zhang
 * @Date: 2022/07/26 13:57
 * @Description:
 **/
@Service
@Slf4j
public class IUserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements IUserService {

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private SmsMessageUtil smsMessageUtil;

    private String TEST_MOBILE = "18325515509";

    private final Integer PASSWORD_LIMIT_LENGTH = 8;

    /**
     * 用户注册
     * 1.手机号注册 获取验证码 校验验证码
     * 2.手机号是否已注册
     *
     * @param userRegisterReqDTO
     * @return
     */
    @Override
    public UserInfoDTO register(UserRegisterReqDTO userRegisterReqDTO) {
        LambdaQueryWrapper<UserDO> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(UserDO::getMobile, userRegisterReqDTO.getMobile());
        UserDO record = this.getOne(lambdaQueryWrapper);
        if (null != record) {
            // 用户已注册，跳转登录页
            throw new UserCoreException(ResponseCodeEnum.USER_ALREADY_EXITS);
        }
        // 校验验证码
        checkVerify(userRegisterReqDTO.getMobile(), SmsTemplateEnum.VERIFY.getType(), userRegisterReqDTO.getCode());
        if (userRegisterReqDTO.getPassword().length() < PASSWORD_LIMIT_LENGTH) {
            // 密码最低8位数
            throw new UserCoreException(ResponseCodeEnum.PASSWORD_PATTERN_ERROR);
        }

        UserDO userDO = new UserDO();
        userDO.setMobile(userRegisterReqDTO.getMobile());
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        userDO.setPassword(bCryptPasswordEncoder.encode(userRegisterReqDTO.getPassword()));
        userDO.setNickName(userRegisterReqDTO.getNickName());
        userDO.setUserName(userRegisterReqDTO.getMobile());
        userDO.setMobile(userRegisterReqDTO.getMobile());
        userDO.setAvatarUrl("https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKnrBD6ZSPYZ8WvHjqIcJ7JXwBszOibbT7150ROaFOtawMnY0G5HvzG2FS1Oy9tA4ZrkuVYbJvYzBA/132");
        userDO.setStatus("init");
        userDO.setCreateBy("user");
        userDO.setUpdateBy("user");
        userDO.setIsDeleted(false);
        userDO.setCreateTime(new Date());
        userDO.setUpdateTime(new Date());
        this.save(userDO);
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        BeanUtils.copyProperties(userRegisterReqDTO, userInfoDTO);
        return userInfoDTO;
    }

    /**
     * 重置密码
     * 1.用户是否存在
     * 2.验证码是否正确
     * 3.替换数据库密码为新密码
     *
     * @param userResetPwdReqDTO
     * @return
     */
    @Override
    public Boolean resetPassword(UserResetPwdReqDTO userResetPwdReqDTO) {
        LambdaQueryWrapper<UserDO> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(UserDO::getMobile, userResetPwdReqDTO.getMobile());
        UserDO record = this.getOne(lambdaQueryWrapper);
        if (null == record) {
            // 用户不存在
            throw new UserCoreException(ResponseCodeEnum.USER_NOT_EXITS);
        }
        // 校验验证码
        checkVerify(userResetPwdReqDTO.getMobile(), SmsTemplateEnum.RESET_PASSWORD.getType(), userResetPwdReqDTO.getCode());
        if (userResetPwdReqDTO.getPassword().length() < PASSWORD_LIMIT_LENGTH) {
            // 密码最低8位数
            throw new UserCoreException(ResponseCodeEnum.PASSWORD_PATTERN_ERROR);
        }
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        record.setPassword(bCryptPasswordEncoder.encode(userResetPwdReqDTO.getPassword()));
        this.updateById(record);
        return Boolean.TRUE;
    }

    /**
     * 用户登录，登录成功返回token
     *
     * @param userLoginReqDTO
     * @return
     */
    @Override
    public String login(UserLoginReqDTO userLoginReqDTO) {
        LambdaQueryWrapper<UserDO> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(UserDO::getMobile, userLoginReqDTO.getUserName());
        UserDO record = this.getOne(lambdaQueryWrapper);
        if (null == record) {
            // 用户不存在
            throw new UserCoreException(ResponseCodeEnum.USER_NOT_EXITS);
        }
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        boolean matches = bCryptPasswordEncoder.matches(userLoginReqDTO.getPassword(), record.getPassword());
        if (!matches) {
            // 用户名或密码错误
            throw new UserCoreException(ResponseCodeEnum.ACCOUNT_OR_PASSWORD_ERROR);
        }
        //创建JWT对象
        String token = TokenUtil.genToken(record.getId().toString(), record.getUserName(), record.getUserCode());
        log.info("token{}", token);
        return token;
    }

    /**
     * 发送验证码
     *
     * @param verifyCodeReqDTO
     * @return
     */
    @Override
    public Boolean sendVerifyCode(VerifyCodeReqDTO verifyCodeReqDTO) {
        if (TEST_MOBILE.equals(verifyCodeReqDTO.getMobile())){
            return Boolean.TRUE;
        }
        SmsTemplateEnum smsTemplateEnum = SmsTemplateEnum.getByType(verifyCodeReqDTO.getVerifyType());
        if (null == smsTemplateEnum) {
            return Boolean.FALSE;
        }
        return smsMessageUtil.sendVerifyCode(verifyCodeReqDTO.getMobile(), smsTemplateEnum);
    }

    /**
     * 校验验证码
     *
     * @param verifyCodeReqDTO
     * @return
     */
    @Override
    public Boolean checkVerifyCode(VerifyCodeReqDTO verifyCodeReqDTO) {
        if (TEST_MOBILE.equals(verifyCodeReqDTO.getMobile())){
            return Boolean.TRUE;
        }
        SmsTemplateEnum smsTemplateEnum = SmsTemplateEnum.getByType(verifyCodeReqDTO.getVerifyType());
        String key = RedisConstant.VERIFY_CODE + smsTemplateEnum.getTemplateCode() + ":" + verifyCodeReqDTO.getMobile();
        if (!redisUtil.keyExists(key)) {
            // 验证码已过期
            throw new UserCoreException(ResponseCodeEnum.VERIFY_CODE_INVALID);
        }
        // 验证码
        String verifyCode = redisUtil.getString(key);
        if (StringUtils.isNotBlank(verifyCode) && verifyCode.equals(verifyCodeReqDTO.getCode())) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public UserInfoDTO info(UserInfoReqDTO userInfoReqDTO) {
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        if (null != userInfoReqDTO.getUserCode() && StringUtils.isNotBlank(userInfoReqDTO.getUserName())) {
            return userInfoDTO;
        }
        LambdaQueryWrapper<UserDO> lambdaQueryWrapper = new LambdaQueryWrapper();
        lambdaQueryWrapper.eq(null != userInfoReqDTO.getUserCode(), UserDO::getId, userInfoReqDTO.getUserCode());
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(userInfoReqDTO.getUserName()), UserDO::getUserName, userInfoReqDTO.getUserName());
        UserDO userDO = this.getOne(lambdaQueryWrapper);
        BeanUtils.copyProperties(userDO, userInfoDTO);
        return userInfoDTO;
    }

    @Override
    public Boolean sendPicVerifyCode(String mobile, String key, HttpServletResponse response) {
        String code = CaptchaUtil.buildCaptcha(null, response);
        log.info("图片验证码已发送:{}", code);
        redisUtil.saveString(getPicCodeKey(mobile, key), code, RedisTimeoutConstants.CACHE_HOLD_TIME_5M);
        return Boolean.TRUE;
    }

    @Override
    public Boolean checkPicVerifyCode(String mobile, String key, String picCode) {
        if (TEST_MOBILE.equals(mobile)){
            return Boolean.TRUE;
        }
        String cacheKey = getPicCodeKey(mobile, key);
        if (!redisUtil.keyExists(cacheKey)) {
            // 验证码已过期
            throw new UserCoreException(ResponseCodeEnum.VERIFY_CODE_INVALID);
        }
        String code = redisUtil.getString(cacheKey);
        if (Objects.equals(code, picCode)){
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public UserInfoDTO userInfoByToken(HttpServletRequest request) {
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        BaseUserInfoDTO baseUserInfoDTO = TokenUtil.parseToken(request);
        if (null != baseUserInfoDTO && null != baseUserInfoDTO.getUserId()) {
            UserDO userDO = this.getById(baseUserInfoDTO.getUserId());
            if (userDO != null) {
                BeanUtils.copyProperties(userDO, userInfoDTO);
            }
        }
        return userInfoDTO;
    }

    private void checkVerify(String mobile, String verifyType, String verifyCode) {
        VerifyCodeReqDTO verifyCodeReqDTO = new VerifyCodeReqDTO();
        verifyCodeReqDTO.setCode(verifyCode);
        verifyCodeReqDTO.setMobile(mobile);
        verifyCodeReqDTO.setVerifyType(verifyType);
        Boolean checkResult = checkVerifyCode(verifyCodeReqDTO);
        if (!checkResult) {
            // 验证码错误
            throw new UserCoreException(ResponseCodeEnum.VERIFY_CODE_ERROR);
        }
    }

    private String getPicCodeKey(String phone, String key){
        return RedisConstant.USER_PHONE_PIC_CODE + phone + RedisConstant.SPLIT + key;
    }
}
